#include "HExpression.h"
#include "HXD1.h"
#include "HIRCode.h"
#include <stdexcept>
#include <cassert>
#include "HWarnings.h"

extern HWarnings warnings;

static inline bool is_numer(const cact_type_t &x)
{
	return (x.base_type == Btype::DOUBLE || x.base_type == Btype::FLOAT
		|| x.base_type == Btype::INT);
}

/*
 * If `x` is `DOUBLE`, `FLOAT` or `INT` and
 * is the same as `y`, and both of them are scalar,
 * that is, they can be added or multed,
 * then return `true`.
 */
static inline bool are_numer_scal(const cact_type_t &x, const cact_type_t &y)
{
	return (is_numer(x)) && x.base_type == y.base_type &&
		x.dim.size() == 0UL && y.dim.size() == 0UL;
}

/*
 * If `x` is `DOUBLE`, `FLOAT`, `INT` or `BOOL` and
 * is the same as `y`, and both of them are scalar,
 * that is, they can be compared (`==` or `!=`),
 * then return `true`.
 */
static bool are_numer_or_bool(const cact_type_t &x, const cact_type_t &y)
{
	if (are_numer_scal(x, y))
		return true;
	if (x.base_type == Btype::BOOL && y.base_type == Btype::BOOL &&
		x.dim.size() == 0UL && y.dim.size() == 0UL)
		return true;
	return false;
}

/*
 * Set `lchild` as `_lchild` and return `_lchild` or `nullptr` on error.
 * This operation is for constructing an expression tree for function call
 * as `unary_expr`, so it is only valid for `FUNC` and `FARG` node!
 */
HExpression * HExpression::set_lchild(HExpression *lchild)
{
	if (_oper != FUNC && _oper != FARG)
		return nullptr;
	if (_lchild != nullptr)
		return nullptr;
	(_lchild = lchild)->_parent = this;
	return _lchild;
}

// HExpression::HExpression(Operation oper, std::vector<HExpression *> &children)
HExpression::HExpression(Operation oper, HExpression *lchild, HExpression *rchild)
{
	_parent = nullptr;
	_psymtb = nullptr;
	assert((_oper = oper) != END);
	assert(lchild != nullptr);
	flag_evaluated = false;

	lchild->_parent = this;
	if (rchild != nullptr)
		// `rchild` is `nullptr` for unary expression
		rchild->_parent = this;
	_lchild = lchild, _rchild = rchild;
	// calc `_etype` automatically
	_etype.dim.clear();
	if (oper != ARR)
	{
		if (lchild->_etype.dim.size() > 0UL ||
			(rchild != nullptr && rchild->_etype.dim.size() > 0UL))
			throw "operation is not for array";
	}
	switch (oper)
	{
	case MOD:	// %
		if (_lchild->_etype.base_type != Btype::INT ||
			_rchild->_etype.base_type != Btype::INT)
			throw "'%' only for int";
	case ADD: case SUB: case MUL: case DIV:	// +, -, *, /
		if (!are_numer_scal(_lchild->_etype, _rchild->_etype))
			throw "operands are not same type numeric scalars";
		_etype.base_type = _lchild->_etype.base_type;
		break;

	case POS: case NEG:	// +, -
		if (!is_numer(_lchild->_etype))
			throw "operand is not numeric scalar";
		_etype.base_type = _lchild->_etype.base_type;
		break;

	case NOT:	// !
		if (_lchild->_etype.base_type != Btype::BOOL)
			throw "operand is not bool";
		_etype.base_type = Btype::BOOL;
		break;

	case LST: case LEQ: case GRT: case GEQ:	// <, <=, >, >=
		if (!are_numer_scal(_lchild->_etype, _rchild->_etype))
			throw "operands are not same type numeric scalars";
	case EQU: case NEQ:	// ==, !=
		if (!are_numer_or_bool(_lchild->_etype, _rchild->_etype))
			throw "operands cannot be compared";
		_etype.base_type = Btype::BOOL;
		break;

	case LOR: case LAND:	// ||, &&
		if (_lchild->_etype.base_type != Btype::BOOL ||
			_rchild->_etype.base_type != Btype::BOOL)
			throw "operands are not both bool";
		_etype.base_type = Btype::BOOL;
		break;

	case ARR:	// [ . ]
		if (_lchild->_etype.dim.size() == 0UL)
			throw "operator[] is not for scalar";
		if (_rchild->_etype.dim.size() > 0UL || _rchild->_etype.base_type != Btype::INT)
			throw "invalid array index";
		_etype.dim.assign(_lchild->_etype.dim.begin() + 1L, _lchild->_etype.dim.end());
		_etype.base_type = _lchild->_etype.base_type;
		break;
	default:
		//assert(0);	// Oops!
		throw std::logic_error(
			std::string("Unknown expression operation: ") + std::to_string(int(oper))
		);
		break;
	}

	// determine whether the expression is const
	if (oper == ARR)
		// if `a[]` is const, we consider `a[i]` is const even if `i` is variable
		_etype.is_const = _lchild->_etype.is_const;
	else
		_etype.is_const = (_lchild->_etype.is_const &&
			(_rchild == nullptr || _rchild->_etype.is_const));
	
	if (opts.opt_merge_const_expr != 0)
	{
		if ((_lchild->flag_evaluated && (_rchild == nullptr || _rchild->flag_evaluated))
		/*
		 * If an array is completely located, we also TRY to evaluate.
		 * But in the evaluation function, whether all indexes are evaluated
		 * should be considered.
		 */
			|| (oper == ARR && _etype.dim.size() == 0ul))
			evaluate();
	}
}


HExpression* operator+(HExpression &e1, HExpression &e2)
{
	HExpression *pne = new HExpression(HExpression::ADD, &e1, &e2);
	return pne;
}

HExpression* operator-(HExpression &e1, HExpression &e2)
{
	HExpression *pne = new HExpression(HExpression::SUB, &e1, &e2);
	return pne;
}

HExpression* operator*(HExpression &e1, HExpression &e2)
{
	HExpression *pne = new HExpression(HExpression::MUL, &e1, &e2);
	return pne;
}

HExpression* operator/(HExpression &e1, HExpression &e2)
{
	HExpression *pne = new HExpression(HExpression::DIV, &e1, &e2);
	return pne;
}

HExpression* operator%(HExpression &e1, HExpression &e2)
{
	HExpression *pne = new HExpression(HExpression::MOD, &e1, &e2);
	return pne;
}

HExpression* operator+(HExpression &exp)
{
	HExpression *pne = new HExpression(HExpression::POS, &exp, nullptr);
	return pne;
}

HExpression* operator-(HExpression &exp)
{
	HExpression *pne = new HExpression(HExpression::NEG, &exp, nullptr);
	return pne;
}

HExpression* operator!(HExpression &exp)
{
	HExpression *pne = new HExpression(HExpression::NOT, &exp, nullptr);
	return pne;
}

HExpression* operator<=(HExpression &e1, HExpression &e2)
{
	HExpression *pne = new HExpression(HExpression::LEQ, &e1, &e2);
	return pne;
}

HExpression* operator<(HExpression &e1, HExpression &e2)
{
	HExpression *pne = new HExpression(HExpression::LST, &e1, &e2);
	return pne;
}

HExpression* operator>=(HExpression &e1, HExpression &e2)
{
	HExpression *pne = new HExpression(HExpression::GEQ, &e1, &e2);
	return pne;
}

HExpression* operator>(HExpression &e1, HExpression &e2)
{
	HExpression *pne = new HExpression(HExpression::GRT, &e1, &e2);
	return pne;
}


HExpression* operator==(HExpression &e1, HExpression &e2)
{
	HExpression *pne = new HExpression(HExpression::EQU, &e1, &e2);
	return pne;
}

HExpression* operator!=(HExpression &e1, HExpression &e2)
{
	HExpression *pne = new HExpression(HExpression::NEQ, &e1, &e2);
	return pne;
}

HExpression* operator||(HExpression &e1, HExpression &e2)
{
	HExpression *pne = new HExpression(HExpression::LOR, &e1, &e2);
	return pne;
}

HExpression* operator&&(HExpression &e1, HExpression &e2)
{
	HExpression *pne = new HExpression(HExpression::LAND, &e1, &e2);
	return pne;
}

HExpression::HExpression(size_t hfteidx)
{
	_lchild = nullptr;
	_hfteid = hfteidx;
	_oper = FUNC; _parent = nullptr;
	_etype.is_const = false;
	_etype.base_type = functable.func(hfteidx).rettype;
	// We are not able to optimize function call
	flag_evaluated = false;

	HExpression *p = this;
	for (size_t i = 1U; i < functable.func(hfteidx).paramtype.size(); ++i)
		p = p->_rchild = new HExpression(this);
	p->_rchild = nullptr;
}

/*
 * When optimization is enabled, try to evaluate an expression
 * if its value can be determined while compiling.
 */
bool HExpression::evaluate()
{
	bool evaluated = false;
	if (_oper != END)
		if (!((_lchild->flag_evaluated && (_rchild == nullptr || _rchild->flag_evaluated))
			|| (_oper == ARR && _etype.dim.size() == 0ul)))
			throw std::logic_error("cannot be evaluated");
	switch (_oper)
	{
	case MOD:
	{
		if (_rchild->_i32val == 0)
		{
			// In RISC-V, division by zero yields zero remainder
			_ival = 0l;
			warnings.add_warning("division by zero");
		}
		else if (_lchild->_i32val == INT32_MIN && _rchild->_i32val == -1)
		{	// In RISC-V, `INT32_MIN` division by -1 yields `INT32_MIN` quotient
			_ival = 0l;
			warnings.add_warning("integer division overflow");
		}
		else
			_ival = _lchild->_i32val % _rchild->_i32val;
		evaluated = true;
	}
		break;
	case ADD:
	{
		switch (_etype.base_type)
		{
		case Btype::DOUBLE:
			_dval = _lchild->_dval + _rchild->_dval;
			break;
		case Btype::FLOAT:
			_fval = _lchild->_fval + _rchild->_fval;
			break;
		default:	// `Btype::INT`
			_ival = _lchild->_i32val + _rchild->_i32val;
			break;
		}
		evaluated = true;
	}
		break;
	case SUB:
	{
		switch (_etype.base_type)
		{
		case Btype::DOUBLE:
			_dval = _lchild->_dval - _rchild->_dval;
			break;
		case Btype::FLOAT:
			_fval = _lchild->_fval - _rchild->_fval;
			break;
		default:	// `Btype::INT`
			_ival = _lchild->_i32val - _rchild->_i32val;
			break;
		}
		evaluated = true;
	}
		break;
	case MUL:
	{
		switch (_etype.base_type)
		{
		case Btype::DOUBLE:
			_dval = _lchild->_dval * _rchild->_dval;
			break;
		case Btype::FLOAT:
			_fval = _lchild->_fval * _rchild->_fval;
			break;
		default:	// `Btype::INT`
			_ival = _lchild->_ival * _rchild->_ival;
			break;
		}
		evaluated = true;
	}
		break;
	case DIV:
	{
		switch (_etype.base_type)
		{
		case Btype::DOUBLE:
			_dval = _lchild->_dval / _rchild->_dval;
			break;
		case Btype::FLOAT:
			_fval = _lchild->_fval / _rchild->_fval;
			break;
		default:	// `Btype::INT`
			if (_rchild->_i32val == 0)
			{	// In RISC-V, division by zero yields ~0 quotient
				_ival = ~int64_t(0);
				warnings.add_warning("division by zero");
			}
			else if (_lchild->_i32val == INT32_MIN && _rchild->_i32val == -1)
			{	// In RISC-V, `INT32_MIN` division by -1 yields `INT32_MIN` quotient
				_ival = _lchild->_i32val;
				warnings.add_warning("integer division overflow");
			}
			else
				_ival = _lchild->_i32val / _rchild->_i32val;
			break;
		}
		evaluated = true;
	}
		break;
	case NEG:
		switch (_etype.base_type)
		{
		case Btype::DOUBLE:
			_dval = -_lchild->_dval;
			break;
		case Btype::FLOAT:
			_fval = -_lchild->_fval;
			break;
		default:	// `Btype::INT`
			_ival = -_lchild->_i32val;
			break;
		}
		evaluated = true;
		break;
	case POS:
		_ival = _lchild->_ival;
		evaluated = true;
		break;
	case LST:
	{
		_ival = 0;
		switch (_lchild->_etype.base_type)
		{
		case Btype::DOUBLE:
			_bval = _lchild->_dval < _rchild->_dval;
			break;
		case Btype::FLOAT:
			_bval = _lchild->_fval < _rchild->_fval;
			break;
		default:	// `Btype::INT`
			_bval = _lchild->_i32val < _rchild->_i32val;
			break;
		}
		evaluated = true;
	}
		break;
	case LEQ:
	{
		_ival = 0;
		switch (_lchild->_etype.base_type)
		{
		case Btype::DOUBLE:
			_bval = _lchild->_dval <= _rchild->_dval;
			break;
		case Btype::FLOAT:
			_bval = _lchild->_fval <= _rchild->_fval;
			break;
		default:	// `Btype::INT`
			_bval = _lchild->_i32val <= _rchild->_i32val;
			break;
		}
		evaluated = true;
	}
		break;
	case GRT:
	{
		_ival = 0;
		switch (_lchild->_etype.base_type)
		{
		case Btype::DOUBLE:
			_bval = _lchild->_dval > _rchild->_dval;
			break;
		case Btype::FLOAT:
			_bval = _lchild->_fval > _rchild->_fval;
			break;
		default:	// `Btype::INT`
			_bval = _lchild->_i32val > _rchild->_i32val;
			break;
		}
		evaluated = true;
	}
		break;
	case GEQ:
	{
		_ival = 0;
		switch (_lchild->_etype.base_type)
		{
		case Btype::DOUBLE:
			_bval = _lchild->_dval >= _rchild->_dval;
			break;
		case Btype::FLOAT:
			_bval = _lchild->_fval >= _rchild->_fval;
			break;
		default:	// `Btype::INT`
			_bval = _lchild->_i32val >= _rchild->_i32val;
			break;
		}
		evaluated = true;
	}
		break;
	case EQU: case NEQ:
	{
		_ival = 0;
		switch (_lchild->_etype.base_type)
		{
		case Btype::DOUBLE:
			_bval = (_lchild->_dval == _rchild->_dval);
			break;
		case Btype::FLOAT: case Btype::INT:
			_bval = (_lchild->_i32val == _rchild->_i32val);
			break;
		default:// Btype::BOOL:
			_bval = (_lchild->_bval == _rchild->_bval);
			break;
		}
		if (_oper == NEQ)
			_bval = !_bval;
		evaluated = true;
	}
		break;
	case NOT:
	{
		_ival = 0;
		_bval = !_lchild->_bval;
		evaluated = true;
	}
		break;
	case LOR:
	{
		_ival = 0;
		_bval = _lchild->_bval || _rchild->_bval;
		evaluated = true;
	}
		break;
	case LAND:
	{
		_ival = 0;
		_bval = _lchild->_bval && _rchild->_bval;
		evaluated = true;
	}
		break;
	case ARR:
		// deal with EXTERNAL const array dereference
	{
		int64_t offset = 0l;
		size_t arr_length = 1ul;
		// the index of the external array in symbol table
		size_t hsteidx;
		HExpression *p;
		for (p = this; p->_oper == ARR; p = p->_lchild)
			;
		if (p->_oper != END)
			throw std::logic_error("invalid leaf node in arr expr");
		hsteidx = p->_hsteid;
		if (p->_etype.is_const && p->_psymtb->parent() == nullptr)
		{
			evaluated = true;
			for (auto & l : p->_etype.dim)
				arr_length *= l;
			p = p->_parent;
			do {
				if (!p->_rchild->flag_evaluated)
				{	// an index is not evaluated
					evaluated = false;
					break;
				}
				offset += p->_rchild->_ival;
				if (p != this)
				{	// not the root, multiply the first dim
					if (p->_etype.dim.size() == 0ul)
						throw std::logic_error("empty dim in non-root ARR node");
					offset *= p->_etype.dim.at(0ul);
				}
			} while (p != this && (p = p->_parent) != nullptr);

			if (evaluated)
			{	// the offset has been evaluated
				_ival = 0l;
				if (uint64_t(offset) >= arr_length)
				{
					warnings.add_warning("array access is out of bound");
				}
				else
				{
					const uint8_t *init_data = (const uint8_t*)(hxd1_ir.extvar(
						hxd1_ir.search_extvar(HSymbolTable::root->symbol(hsteidx).id)
					).data);
					if (init_data != nullptr)
						elecpy(&_ival,
							init_data + uint64_t(offset) * cactsize(_etype.base_type),
							_etype.base_type);
				}
			}
		}
		else
			evaluated = false;
	}
		break;
	case END:
		// deal with a EXTERNAL const symbol
		if (_psymtb != nullptr && _psymtb->parent() == nullptr
			&& _etype.is_const && _etype.dim.size() == 0ul)
		{
			_ival = 0l;
			const uint8_t *init_data = (const uint8_t*)(hxd1_ir.extvar(
				hxd1_ir.search_extvar(HSymbolTable::root->symbol(_hsteid).id)
			).data);
			if (init_data != nullptr)
				elecpy(&_ival, init_data, _etype.base_type);
			evaluated = true;
		}
		else
			evaluated = false;
		break;
	default:
		throw std::logic_error("evaluating error node");
		break;
	}

	return flag_evaluated = evaluated;
}